class ActivityManager(object):
    """This class manages a set of activities, keeping track of which activities were already 
    assigned and which are currently assignable."""
    ALREADY_ASSIGNED = "activity has already been assigned"
    UNASSIGNED_PREDECESSORS = "some predecessors are still unassigned"
    
    def __init__(self, activities=(), assigned=()):
        self.sequence = []
        self.all = set()
        self.assigned = set()
        self.assignable = set()
        # +++
        self.extend(activities)
        for activity in assigned:
            self.mark_assigned(activity)
            
    def append(self, activity):
        if activity in self.all:
            raise ValueError("activity already added to manager")
        self.sequence.append(activity)
        self.all.add(activity)
        if self.is_assignable(activity):
            self.assignable.add(activity)
            
    def extend(self, activities):
        for activity in activities:
            self.append(activity)
            
    def mark_assigned(self, activity):
        """Mark an activity as assigned. The activity *must* be in the assignable set, otherwise 
        an error is raised. Marking an activity as assigned may cause some of its direct successors 
        to become assignable."""
        if activity not in self.assignable:
            raise Exception("activity is not assignable")
        self.assigned.add(activity)
        self.assignable.remove(activity)
        self.assignable.update(a for a in activity.succ if self.is_assignable(a))
        
    def mark_unassigned(self, activity):
        """This method does the opposite of mark_assigned(). The activity must be assigned, and 
        none of its successors may be assigned. The activity is moved to the assignable set, 
        and all its assignable successors become unassignable (since at least one of their 
        predecessors is now unassigned)."""
        if activity not in self.assigned:
            raise Exception("activity is not assigned")
        if not activity.succ.isdisjoint(self.assigned):
            raise Exception("cannot unassign activity with one or more assigned successors")
        self.assigned.remove(activity)
        self.assignable.add(activity)
        self.assignable.difference_update(activity.succ)
        
    def check_assignable(self, activity):
        """Check if an activity is assignable, i.e. the activity is not yet assigned AND all its 
        predecessors are already assigned. An error message is returned if either of these 
        conditions is violated, otherwise 'None' is returned."""
        if activity in self.assigned:
            return ActivityManager.ALREADY_ASSIGNED
        if not self.assigned.issuperset(activity.pred):
            return ActivityManager.UNASSIGNED_PREDECESSORS
        return None
        
    def is_assignable(self, activity):
        """Return a boolean indicating whether the argument activity is assignable or not."""
        return self.check_assignable(activity) is None
        
    def feasible_subset(self, size):
        """Compute a subset of activities such that each activity can be fulfilled if the other 
        activities in the subset are also fulfilled. Preferred activities are the ones earliest in 
        the sequence."""
        # if the requested size is greater than or equal to the number of 
        # unassigned activities, we simply compute that set and return it
        if size >= len(self.all) - len(self.assigned):
            return self.all - self.assigned
        
        index_of = {a: i for i, a in enumerate(self.sequence)}
        activities = []
        # in each iteration, find the assignable activity with minimum index
        for _ in xrange(size):
            activity = min(self.assignable, key=index_of.__getitem__)
            activities.append(activity)
            self.mark_assigned(activity)
        # restore original state
        for activity in reversed(activities):
            self.mark_unassigned(activity)
        return set(activities)
        
